/*
 * $Id: MagnetMonitorDataProcessor.java,v 1.5 2009/10/27 22:58:03 afaichun Exp $
 * 
 * Copyright (C) 2008 General Electric Company. All Rights Reserved.
 * 
 * This software is the confidential and proprietary information of the General
 * Electric Company (GE). You shall not disclose this software and shall use it
 * only in accordance with the terms of the license agreement you entered into
 * with GE.
 * 
 * GE MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
 * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
 * NON-INFRINGEMENT. GE SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
 * AS A RESULT OF USING, MODIFYING, OR DISTRIBUTING THIS SOFTWARE OR ITS
 * DERIVATIVES.
 */
package com.ge.healthcare.autosc.processor.magmon;

import java.io.File;
import java.io.PrintWriter;

import org.springframework.util.Assert;

import com.ge.healthcare.autosc.common.ASCLogger;
import com.ge.healthcare.autosc.common.ASCUtil;
import com.ge.healthcare.autosc.common.database.dao.IFileTransferDetailsDAO;
import com.ge.healthcare.autosc.common.database.dao.ITaskFtRecordDAO;
import com.ge.healthcare.autosc.common.database.pojo.ActFileTransferDetails;
import com.ge.healthcare.autosc.common.database.pojo.ActTask;
import com.ge.healthcare.autosc.common.dbLogger.StepLogger;
import com.ge.healthcare.autosc.common.exceptions.BasicException;
import com.ge.healthcare.autosc.common.exceptions.BasicRuntimeException;
import com.ge.healthcare.autosc.common.util.CheckSumUtil;
import com.ge.healthcare.autosc.common.util.TimeFormatStandard;
import com.ge.healthcare.autosc.common.util.dataItem.IASCTextDataItem;
import com.ge.healthcare.autosc.common.util.dataItem.IASCTextReader;
import com.ge.healthcare.autosc.processor.PrcCodeConstants;
import com.ge.healthcare.autosc.processor.exception.MagnetMonitorProcessorException;
import com.ge.healthcare.autosc.processor.exception.MagnetMonitorSiteException;
import com.ge.healthcare.autosc.processor.exception.Y2KFixerGeneratorException;
import com.ge.healthcare.autosc.processor.magmon.y2k.Year2000Fixer;
import com.ge.healthcare.autosc.processor.magmon.y2k.Year2000FixerGenerator;
import com.ge.healthcare.autosc.processor.shemjob.JobDirRequestData;
import com.ge.healthcare.autosc.processor.shemjob.guidance.dataset.MagmonDataSet;

public class MagnetMonitorDataProcessor {

	private static IFileTransferDetailsDAO fileTransferDetailsDAO = null;
	private static ITaskFtRecordDAO taskFtRecordDAO = null;
	private IMagnetMonitorSite magMonSite = null;
	private MagmonDataSet magMonDataSet = null;
	private JobDirRequestData jobDirData = null;
	
	public void init() {
		Assert.notNull(taskFtRecordDAO,
			"ActFtRecordDAO object is required. Use setActFtRecordDAO() to provide one.");
		Assert.notNull(fileTransferDetailsDAO,
			"ActFileTransferDetailsDAO object is required. Use setActFileTransferDetailsDAO() to provide one.");
	}
	
	/**
	 * This is for Spring configuration. PLEASE USE OTHER CONSTRUCTORS
	 */
	public MagnetMonitorDataProcessor() {}
	
	public MagnetMonitorDataProcessor(JobDirRequestData jobDirData, 
									  IMagnetMonitorSite magMonSite, 
									  MagmonDataSet magMonDataSet)
		throws MagnetMonitorProcessorException {
		if(magMonSite == null || magMonDataSet == null || jobDirData == null) {
			throw new MagnetMonitorProcessorException("Programming error. Incorrect use of class " +
					MagnetMonitorDataProcessor.class.getName(), PrcCodeConstants.PRC_F_SHM_015);
		}
		this.magMonSite = magMonSite;
		this.magMonDataSet = magMonDataSet;
		this.jobDirData = jobDirData;
	}

	/**
	 * Process incoming data based on the identifier representing data being
	 * processed in the sites directory.
	 * @param key	The key used to identify the data set being processed
	 * @param inputReader	Reader that is used to read data set
	 * @throws MagnetMonitorProcessorException
	 */
	public void process(String key, 
						IASCTextReader inputReader,
						ActTask task)
		throws MagnetMonitorProcessorException {
		final int functionId = magMonDataSet.getFunction();
	    String[] argList = magMonDataSet.getArgList();
	    
        switch ( functionId )
        {
          case MagmonDataSet.COPY_SAME_NAME_DATA_FUNC:
            copySameNameDataFunc( key, inputReader, task);
            break;
          case MagmonDataSet.IGNORE_DATA_FUNC:
        	StepLogger.addStepLog("Ignore " + key);
            inputReader.silentlyClose();
            break;
          case MagmonDataSet.COPY_DATA_MOVE_OLD_FUNC:
            copyDataMoveOldFunc( key, 
            					 inputReader, 
            					 argList[0], 
            					 argList[1], 
            					 task);
            break;
          case MagmonDataSet.COPY_SORTED_FUNC:
            copySortedFunc( key,
                            inputReader,
                            argList[0],
                            argList[1],
                            argList[2],
                            task);
            break;
          case MagmonDataSet.INTEGRATE_DATA_FUNC:
            integrateDataFunc( key,
                               inputReader,
                               argList[0],
                               argList[1],
                               argList[2],
                               argList[3],
                               task);
            break;
          default:
            String message = new StringBuffer().append("No processing defined for function: ")
            				 .append(functionId).append("  data set identifier: ")
                             .append(key).toString();
            throw new MagnetMonitorProcessorException(message, PrcCodeConstants.PRC_F_SHM_013);
        }
	}   
	
	public void setActFileTransferDetailsDAO(IFileTransferDetailsDAO fileTransferDetailsDAO) {
		MagnetMonitorDataProcessor.fileTransferDetailsDAO = fileTransferDetailsDAO;
	}
	
	public void setActTaskFtRecordDAO(ITaskFtRecordDAO taskFtRecordDAO) {
		MagnetMonitorDataProcessor.taskFtRecordDAO = taskFtRecordDAO;
	}
	
    /**
     * Copy the input data set to the site item with the same identifier.
     *  This method will close the input reader before returning.  If it
     *  exists, the old copy of the destination data item is deleted before
     *  making the new copy.
     * <pre>
     * This method implements the following sequence of operations:
     *  * Copy the records from the source input reader to the job directory
     *    data item, then close both the reader and the job directory item.
     *    (ActFileTranderDetails record is created)
     *  * Remove the existing destination data item.
     *  * copy the data from the job directory data item to the deinstation
     *    data item.
     * </pre>
     *
     * @param key The key used to identify the data set being processed
     *  and also the data item to which it is being copied.
     *
     * @param inputReader The reader used to read the data set.
     *
     * @exception AscServiceAbortException If an error is detected copying
     *  the data set.
     */
    private void copySameNameDataFunc( String sourceItemId, 
    								   IASCTextReader inputReader,
    								   ActTask task)
        throws MagnetMonitorProcessorException
    {
    	String methodName = "copySameNameDataFunc";
    	boolean status = false;
        IASCTextDataItem destItem = null;
        ASCLogger.info(this.getClass(), methodName, "Copying " + sourceItemId + " to site");
		try
        {
			copyToJobDir(task, sourceItemId, inputReader);
			
			destItem = magMonSite.get( sourceItemId );
	        if ( destItem.exists() ) {
	            destItem.delete();
	        }
        	IASCTextDataItem jobDirDataItem = jobDirData.getDataItem(sourceItemId);
       		jobDirDataItem.copy(destItem);
            status = true;
            
        } catch (MagnetMonitorSiteException mmse) {
        	throw new MagnetMonitorProcessorException(mmse);
        } catch ( BasicException be ) {
            throw new MagnetMonitorProcessorException(be);
        } finally {
        	inputReader.silentlyClose();
            createTaskFtRecord(task.getTaskId(), 
            				   jobDirData.getFtDetails(sourceItemId), 
            				   destItem, 
            				   status);
        }
    }
    
    /**
     * Copy the input data set to the site item with the specified identifier.
     *  This method will close the input reader before returning.  If it
     *  exists, the old copy of the destination data item is moved to the
     *  specified data item.
     * <pre>
     * This method implements the following sequence of operations:
     *  * Copy the records from the source input reader to the job directory
     *    data item, the close both reader and the job directory data item.
     *    (ActFileTranderDetails record is created)
     *  * Move the destination data item to the old data item.
     *  * Open the destination data item for writing.
     *  * Copy data from job directory data item to the destination data item.
     * </pre>
     *
     * @param sourceItemId The key used to identify the source data set
     *  being processed.
     *
     * @param sourceInputReader The reader used to read the data set.
     *
     * @param destId The key used to identify the data item to which
     *  the source data is being copied.
     *
     * @param oldId The key used to identify the data item to which
     *  the old copy of the data is moved.
     *
     * @exception AscServiceAbortException If an error is detected copying
     *  the data set.
     */
    private void copyDataMoveOldFunc( String sourceItemId,
                                      IASCTextReader inputReader,
                                      String destId,
                                      String oldId,
                                      ActTask task)
        throws MagnetMonitorProcessorException
    {
    	String methodName = "copyDataMoveOldFunc";
        ASCLogger.info(this.getClass(), methodName, "Copying " + sourceItemId + " to site data item " + destId);
        PrintWriter destWriter = null;
        boolean status = false;
        IASCTextDataItem destItem = null;
        try
        {
        	copyToJobDir(task, destId, inputReader);
        	
            destItem = magMonSite.get( destId );
            IASCTextDataItem oldItem = magMonSite.get( oldId );

            if ( destItem.exists() ) {
                destItem.move( oldItem );
            }
            
            IASCTextDataItem jobDirDataItem = jobDirData.getDataItem(destId);
       		jobDirDataItem.copy(destItem);
            status = true;
        } catch (MagnetMonitorProcessorException mmpe) {
        	throw mmpe;
        } catch ( BasicException be ) {
            throw new MagnetMonitorProcessorException(be);
        } finally {
        	inputReader.silentlyClose();
        	ASCUtil.silentlyClose(destWriter);
        	createTaskFtRecord(task.getTaskId(), 
        					   jobDirData.getFtDetails(destId), 
        					   destItem, 
        					   status);
        }
    }

    /**
     * Sort the input data set, removing duplicates, placing the results in
     *  the specified destination site item.  This method will close the
     *  input reader before returning.  If it exists, the old copy of
     *  the destination data itme is moved to the specified data item.
     *  The temporary data item is deleted if the processing is successful.
     * <pre>
     * This method implements the following sequence of operations:
     *  * Open the temporary data item for writing.
     *  * Move the destination data item to the old data item.
     *  * Copy the records from the source input reader to the temporary
     *     data item, then close both the reader and the temporary item.
     *  * Sort the records from the temporary data item into the job
     *    directory data item, removing duplicates.(ActFileTranderDetails
     *    record is created)   
     *  * Sort the records from the temporary data item into the
     *     destination data item, removing duplicates.
     *  * Remove the temporary data item.
     * </pre>
     *
     * @param sourceItemId The key used to identify the source data set
     *  being processed.
     *
     * @param sourceInputReader The reader used to read the source data set.
     *
     * @param destId Identifier of the destination data item on the site.
     *  The destination initially contains the old copy of the data, which
     *  will be overwritten.
     *
     * @param oldId Identifier of the data item on the site which will
     *  contain the old copy of the data.
     *
     * @param tempId Identifier of the data item on the site used as the
     *  temporary data store.
     *
     * @exception AscServiceAbortException If an error is detected processing
     *  the data.
     */
    private void copySortedFunc( String sourceItemId,
                                 IASCTextReader inputReader,
                                 String destId,
                                 String oldId,
                                 String tempId,
                                 ActTask task)
        throws MagnetMonitorProcessorException
    {
    	String methodName = "copySortedFunc";
        ASCLogger.info(this.getClass(), methodName, "Sorting " + sourceItemId + " into site data item " + destId);
        boolean status = false;
        PrintWriter tempWriter = null;
        IASCTextDataItem destItem = null;
        try
        {
            destItem = magMonSite.get( destId );
            IASCTextDataItem oldItem = magMonSite.get( oldId );
            IASCTextDataItem tempItem = magMonSite.get( tempId );

            tempWriter = tempItem.openForWriting( false );
            if ( destItem.exists() ) {
                destItem.move( oldItem );
            }
            copy( inputReader, tempWriter );
            sortAndCopyToJobDir(task, destId, tempItem);
            tempItem.sortAndRemoveDuplicates( destItem );
            tempItem.delete();
            status = true;
        } catch (BasicException be) {
            throw new MagnetMonitorProcessorException(be);
        } finally {
        	inputReader.silentlyClose();
        	ASCUtil.silentlyClose(tempWriter);
        	createTaskFtRecord(task.getTaskId(),
        					   jobDirData.getFtDetails(destId), 
        					   destItem, 
        					   status);
        }
    }
    
    /**
     * Integrate the input data set with a site item by sorting the
     *  result and removing duplicates.  This method will close the
     *  input reader before returning.
     * <pre>
     * This method implements the following sequence of operations:
     *  * Open the temporary data item for writing.
     *  * Move the target data item to the old data item.
     *  * Copy the records from the old data item to the temporary
     *     data item, filtering the records using the fixer class,
     *     leaving the temporary data item open.
     *  * Copy the records from the source input reader to the temporary
     *     data item, then close both the reader and the temporary item.
     *  * Sort the records from the temporary data item into the target
     *     data item, removing duplicates.
     *  * Remove the temporary data item.
     * </pre>
     *
     * @param sourceItemId The key used to identify the data set being
     *  processed.
     *
     * @param sourceInputReader The reader used to read the source data set.
     *
     * @param targetId Identifier of the target data item on the site.
     *  The target is the old copy of the data to which the input data is
     *  being integrated and is also the result of the integration.
     *
     * @param oldId Identifier of the data item on the site which will
     *  contain the old copy of the data.
     *
     * @param tempId Identifier of the data item on the site used as the
     *  temporary data store.
     *
     * @param fixerClassName The complete name of the class used to fix
     *  the records from the existing data item so that they are year-2000
     *  compliant.
     *
     * @exception AscServiceAbortException If an error is detected
     *  integrating the data.
     */
    private void integrateDataFunc( String sourceItemId,
                                    IASCTextReader inputReader,
                                    String targetId,
                                    String oldId,
                                    String tempId,
                                    String fixerClassName,
                                    ActTask task )
        throws MagnetMonitorProcessorException
    {
    	boolean status = false;
        Year2000Fixer fixer = null;
        IASCTextDataItem targetItem = null;
        try
        {
            targetItem = magMonSite.get( targetId );
            IASCTextDataItem oldItem = magMonSite.get( oldId );
            IASCTextDataItem tempItem = magMonSite.get( tempId );
            fixer = Year2000FixerGenerator.getYear2000Fixer(fixerClassName);
            integrateDataFunc( sourceItemId,
                    		   inputReader,
                    		   targetItem,
                    		   oldItem,
                    		   tempItem,
                    		   fixer,
                    		   task);
            status = true;
        } catch (MagnetMonitorSiteException mmse) {
        	throw new MagnetMonitorProcessorException(mmse);
        } catch ( Y2KFixerGeneratorException yge ) {
        	throw new MagnetMonitorProcessorException("Year-2000 fixer class " + fixerClassName +
        											  " not found", yge);		
        } finally {
        	createTaskFtRecord(task.getTaskId(),
					   jobDirData.getFtDetails(targetId), 
					   targetItem, 
					   status);
        }
    }
    
    /**
     * Integrate the input data set with a site item by sorting the
     *  result and removing duplicates.  This method will close the
     *  input reader before returning.
     * <pre>
     * This method implements the following sequence of operations:
     *  * Open the temporary data item for writing.
     *  * Move the target data item to the old data item.
     *  * Copy the records from the old data item to the temporary
     *     data item, filtering the records using the fixer class,
     *     leaving the temporary data item open.
     *  * Copy the records from the source input reader to the temporary
     *     data item, then close both the reader and the temporary item.
     *  * Sort the records from the temporary data item into the target
     *     data item, removing duplicates.
     *  * Remove the temporary data item.
     * </pre>
     *
     * @param sourceItemId The key used to identify the data set being
     *  processed.
     *
     * @param sourceInputReader The reader used to read the source data set.
     *
     * @param targetItem The target data item, which is the old copy of the
     *  data to which the input data is being integrated and is also the
     *  result of the integration.
     *
     * @param oldItem The data item which will contain the old copy of the data.
     *
     * @param tempItem The data item used as the temporary data store.
     *
     * @param fixer The class used to fix the records from the existing
     *  data item so that they are year-2000 compliant.
     *
     * @exception AscServiceAbortException If an error is detected integrating
     *  the data.
     */
    private void integrateDataFunc( String sourceItemId,
                                    IASCTextReader inputReader,
                                    IASCTextDataItem targetItem,
                                    IASCTextDataItem oldItem,
                                    IASCTextDataItem tempItem,
                                    Year2000Fixer fixer,
                                    ActTask task)
        throws MagnetMonitorProcessorException
    {
        PrintWriter tempWriter = null;
        try
        {
            tempWriter = tempItem.openForWriting( false );
            if ( targetItem.exists() )
            {
                targetItem.move( oldItem );
            }
            if ( oldItem.exists() )
            {
                copy( oldItem, tempWriter, tempItem.toString(), fixer );
            }
            copy( inputReader, tempWriter );
            sortAndCopyToJobDir(task, targetItem.getDataSourceFile().getName(), tempItem);
            tempItem.sortAndRemoveDuplicates( targetItem );
            tempItem.delete();
        } catch (MagnetMonitorProcessorException mmpe) {
        	throw mmpe;
        } catch (BasicException be) {
            throw new MagnetMonitorProcessorException(be);
        } finally {
            inputReader.silentlyClose();
            ASCUtil.silentlyClose( tempWriter );
        }
    }
    
    /**
     * Read all the records from the input data item, fix any year-2000
     *  compliance issues, and write the fixed records to the destination
     *  writer.  The writer is left open.
     *
     * @param inputItem The data item containing the source data.
     *
     * @param outputWriter The writer to which the fixed records are written.
     *
     * @param outputName The name used to identify the output data store
     *  for error messages.
     *
     * @param fixer The class used to fix the records from the existing
     *  data item so that they are year-2000 compliant.
     *
     * @exception AscServiceAbortException If an error is detected copying or
     *  fixing the data.
     */
    private void copy( IASCTextDataItem inputItem,
                       PrintWriter outputWriter,
                       String outputName,
                       Year2000Fixer fixer ) throws MagnetMonitorProcessorException
    {
    	try {
	    	IASCTextReader inputReader = inputItem.openForReading();
	        for ( String currentLine = inputReader.readLine();
	              currentLine != null;
	              currentLine = inputReader.readLine() )
	        {
	            String fixedLine = fixer.getCompliant( currentLine );
	            outputWriter.println( fixedLine );
	        }
    	} catch (BasicException be) {
    		throw new MagnetMonitorProcessorException(be);
    	}
        //inputReader.close();
        if ( outputWriter.checkError() )
        {
            String message = new StringBuffer().append("Write error detected copying records from ")
                             .append(inputItem).append(", fixing year-2000 compliance, to ")
                             .append(outputName).toString();
            throw new MagnetMonitorProcessorException(message);
        }
    }
    
    /**
     * Copy the entire contents of the input reader into the destination
     *  writer, leaving the writer open when done.  This is used for
     *  transferring text data.  The input reader is closed before
     *  returning.
     *
     * @param inputReader The input source.
     *
     * @param outputWriter The destination for the lines read from the
     *  input source.
     *
     * @exception AscServiceAbortException If an I/O error is detected.
     */
    private void copy( IASCTextReader inputReader, PrintWriter outputWriter )
        throws MagnetMonitorProcessorException
    {
    	try {
	        for ( String currentLine = inputReader.readLine();
	              currentLine != null;
	              currentLine = inputReader.readLine() )
	        {
	            outputWriter.println( currentLine );
	        }
    	} catch (BasicException be) {
    		throw new MagnetMonitorProcessorException(be);
    	}
        //inputReader.close();
        if ( outputWriter.checkError() )
        {
        	throw new MagnetMonitorProcessorException("Write error detected copying input to output");
        }
    }
    
    private void copyToJobDir(ActTask task, String sourceItemId, IASCTextReader inputReader) 
    	throws MagnetMonitorProcessorException {
    	PrintWriter outWriter = null;
    	try {
	    	IASCTextDataItem jobDirItem = jobDirData.newDataFile(sourceItemId);
	        if ( jobDirItem.exists() ) {
	            jobDirItem.delete();
	        }
	    	outWriter = jobDirItem.openForWriting( false );
	    	copy( inputReader, outWriter );
	    	ActFileTransferDetails ftDetail = createFileTransferDetails(task, jobDirItem);
	    	jobDirData.addDataItem(sourceItemId, jobDirItem, ftDetail);
    	} catch (MagnetMonitorProcessorException mmpe) {
    		throw mmpe;
    	} catch (BasicException be) {
    		throw new MagnetMonitorProcessorException(be);
    	} finally {
    		inputReader.silentlyClose();
    		ASCUtil.silentlyClose(outWriter);
    	}
    }
    
    private void sortAndCopyToJobDir(ActTask task, String destId, IASCTextDataItem textDataItem) 
    	throws MagnetMonitorProcessorException {

		try {
	    	IASCTextDataItem jobDirItem = jobDirData.newDataFile(destId);
	        if ( jobDirItem.exists() ) {
	            jobDirItem.delete();
	        }
	        textDataItem.sortAndRemoveDuplicates(jobDirItem);
	    	ActFileTransferDetails ftDetail = createFileTransferDetails(task, jobDirItem);
	    	jobDirData.addDataItem(destId, jobDirItem, ftDetail);
		} catch (BasicException be) {
			throw new MagnetMonitorProcessorException(be);
		} 
	}
    
	private ActFileTransferDetails createFileTransferDetails(ActTask task, IASCTextDataItem dataItem) {
		String methodName = "createFileTransferDetails";
		ActFileTransferDetails ftDetails = new ActFileTransferDetails();
		ftDetails.setRequestId(task.getRequestId());
		ftDetails.setShemDirection();
		File sourceFile = dataItem.getDataSourceFile();
		ftDetails.setContentSize(new Long(sourceFile.length()));
		ftDetails.setFileName(sourceFile.getName());
		ftDetails.setInternalCommFile(false);
		ftDetails.setFileDir(sourceFile.getParent());
		ftDetails.setStartTime(TimeFormatStandard.getNowMillis());
		ftDetails.setEndTime(TimeFormatStandard.getNowMillis());
		ftDetails.setCompleted();
		try {
			ftDetails.setCheckSum(CheckSumUtil.getMD5Checksum(sourceFile.getAbsolutePath()));
		} catch (Exception e) {
			String errorStr = "Failed create checksum for file " + sourceFile.getAbsolutePath();
			ASCLogger.error(this.getClass(), methodName, errorStr, e);
			throw new BasicRuntimeException(errorStr, e);
		}
		fileTransferDetailsDAO.save(ftDetails);
		return ftDetails;
	}

    private void createTaskFtRecord(Long taskId, 
    								ActFileTransferDetails ftDetail,
    								IASCTextDataItem dataItem, 
    								boolean success) {
    	
    	if(dataItem != null && ftDetail != null) {
    		String filePath = dataItem.getDataSourceFile().getPath();
	    	if(success) {
	    		taskFtRecordDAO.saveSuccess(ftDetail.getFtId(), taskId, filePath);
	    	} else {
	    		taskFtRecordDAO.saveFailure(ftDetail.getFtId(), taskId, filePath);
	    	}
    	}
    }
}
